#ifdef RFM_LOCAL_C_HASH
// 使用编译时定义的宏 RFM_LOCAL_C_HASH 来初始化这个常量指针
// 这个变量会成为 .so 文件中的一个符号，可以被 dlsym 找到
const char * const rfm_local_c_file_hash = RFM_LOCAL_C_HASH;
#else
// 如果编译时没有定义 RFM_LOCAL_C_HASH 宏 (例如手动编译时忘了加 -D 参数)
// 就发出警告，并将符号定义为 NULL，这样 rfm.c 加载时能发现哈希缺失
#warning "RFM_LOCAL_C_HASH is not defined during compilation. Hash checking willlikely fail."
const char * const rfm_local_c_file_hash = NULL;
#endif

#include <gtk/gtk.h>
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>
#include <limits.h>
#include <assert.h>
#include <json-glib/json-glib.h>
#include <rfm_addon.h>

#ifdef rfmChinese
	#define ALL_AI_DIALOG "locate AI对话"
	#define TREE_AI_DIALOG "find . AI对话"
	#define RunActionFindDRelated "查找直接关联AI对话(根据ID,历史ID),使用locate,注意updatedb"
	#define RunActionFindRelated "查找关联AI对话(根据ID,历史ID),使用locate,注意updatedb"
	#define RunActionFindDRelatedAndAttachedFiles "查找直接关联AI对话(根据ID,历史ID),及附加文件"
	#define RunActionFindRelatedAndAttachedFiles "查找关联AI对话(根据ID,历史ID),及附加文件"
	#define RunActionAddAttachedFiles "列出选中AI对话及附加文件"
#else
	#define ALL_AI_DIALOG "locate AI history"
	#define TREE_AI_DIALOG "find . AI history"
	#define RunActionFindDRelated "locate directly related AI history(based on ID, history ID)"
	#define RunActionFindRelated "locate related AI history(based on ID, history ID)"
	#define RunActionFindDRelatedAndAttachedFiles "locate directly related AI history(based on ID, history ID) and attached files"
	#define RunActionFindRelatedAndAttachedFiles "locate related AI history(based on ID, history ID) and attached files"
	#define RunActionAddAttachedFiles "list selected AI dialog and attached files"
#endif

extern char rfm_historyFile_ParentDirectory[PATH_MAX];
extern void load_history_for_prompt();
extern void save_current_history();
extern void add_toolbar_buttons(GtkWidget ***buttons, const RFM_ToolButton button_definitions[], const size_t size);
extern void add_file_menuitems(GtkWidget ***menuitems, const RFM_MenuItem menuitem_definitions[], const size_t size);
extern void remove_toolbar_buttons(GtkWidget ***buttons, const size_t size);
extern void remove_file_menuitems(GtkWidget ***menuitems, const size_t size);
extern gboolean g_spawn_wrapper(CALLER_OWN const char **action,
                         CALLER_OWN const GList *file_list, const int run_opts,
                         FREE const gchar **env, const char *dest_path,
                         const gboolean async,
                         const void (*callbackfunc)(gpointer),
                         CALLER_OWN const gpointer callbackfuncUserData,
                         const gboolean output_read_by_program);
extern gboolean g_spawn_wrapper_(CALLER_OWN const GList *file_list, CALLER_OWN const char *dest_path, FREE_FULL RFM_ChildAttribs * childAttribs);
extern int ProcessJsonInCmdOutputFromSearchResult(CALLER_OWN char *oneline, gboolean new_search, char** cmdtemplate, RFM_FileAttributes* fileAttributes,RFM_ChildAttribs* child_attribs);
extern const stdin_cmd_interpretor stdin_cmd_interpretors[];
extern gchar shell_cmd_buffer[];
extern gchar* stdin_cmd_template_bash[];
extern gchar* stdin_cmd_template_bash_newVT_nonHold[];
extern gchar* stdin_cmd_template_bash_newVT_nonHold_with_e[];

RFM_ADDON_RESOURCE_TYPE addon_resource = {0};

#define G_LOG_DOMAIN "rfm"

//下面这一句一个字符,甚至一个空格都不能改,全局addon加载程序通过grep这个字符串来识别全局addon
const enum RFM_ADDON_TYPE addon_type = Global;


typedef struct {
  gchar *calling_script;
  gchar *host_url;
  gchar *model_name;
  gchar *term_prompt; // This is the prompt such b> shown in term, not the prompt for LLM
  gchar *activationKey;
  gboolean InNewTerm;
  gchar *model_temporature;
  gchar *API_key;
} AI_model_parameter;

static AI_model_parameter *AI_models;
static size_t AI_models_array_size;
static int current_AI_model_index = 0;
static int current_tmp_prefix_AI_model_index = -1;

#define PRESS_ENTER_TO_CLOSE_WINDOW "按回车键关闭当前窗口"

NO_FREE gchar** stdin_LLM_prompt(CALLER_OWN gchar* user_input_prompt, gboolean inNewVT) {
  g_debug("stdin_LLM_prompt called, current_AI_model term_prompt:%s",AI_models[current_AI_model_index].term_prompt);
  int model_index = current_tmp_prefix_AI_model_index>=0 ? current_tmp_prefix_AI_model_index : current_AI_model_index;
  current_tmp_prefix_AI_model_index = -1;
  if (AI_models[model_index].InNewTerm){
    snprintf(shell_cmd_buffer,PATH_MAX,"set -o history; %s %s %s %s %s; read -p '\033[33m%s\033[0m'; exit \$? 2>/dev/null", AI_models[model_index].calling_script, AI_models[model_index].host_url,AI_models[model_index].model_name, AI_models[model_index].model_temporature, AI_models[model_index].API_key, PRESS_ENTER_TO_CLOSE_WINDOW);
    gchar * term_e = getenv("RFM_TERM_E"); // 获取环境变量
    // 根据环境变量的值选择正确的模板
    if (term_e!=NULL && strcmp(term_e,"")!=0) {
        return stdin_cmd_template_bash_newVT_nonHold_with_e; // 返回带 -e 的模板
    } else {
        return stdin_cmd_template_bash_newVT_nonHold; // 返回不带 -e 的模板
    }
  }else {
    snprintf(shell_cmd_buffer,PATH_MAX,"set -o history; %s %s %s %s %s; exit \$? 2>/dev/null",AI_models[model_index].calling_script, AI_models[model_index].host_url, AI_models[model_index].model_name, AI_models[model_index].model_temporature, AI_models[model_index].API_key);
    return stdin_cmd_template_bash;
  }
}


const stdin_cmd_interpretor addon_stdin_cmd_interpretors[] = {
        //Name             activationKey          prompt            cmdTransformer       HistoryFilename
	{"AI Model",                 "a>",        "a",              stdin_LLM_prompt,    ".rfm_history_AI" }, //The prompt of AI will not show. The term_prompt of default AI_models will show instead. The acitivationKey of AI and that of the default AI_models have the same effect.
};


// 1. 检查输入的命令是否是切换至当前addon定义的stdin_cmd_interpretor,如果是，则返回其指针，如果不是，函数最后返回NULL。也就是说，返回值是用来识别当前输入命令readlineResult是否是切换命令解释器的，识别的是命令解释器的变化，而不关心当前命令解释器是啥
// 2. 检查输入的命令是否以当前addon定义的activationKey为前缀，如果是，设置effective_cmd_interpretor_pointer
// 3. 检查 (*effective_cmd_interpretor_pointer == &(addon_stdin_cmd_interpretors[0]))，是则设置 addon_dir。这里effective_cmd_interpreter_pointer的值可能是函数调用的输入参数，也可能是本函数上面第二步设置的。rfm里本函数的调用函数，就是通过addon_dir是否为空来判断是否需要用 Addon cmd interpretor 来解释执行当前输入命令readlineResult的。
// TODO: 在通过 prefix 使用AI_mode的情况下，命令history可能设置不正确
stdin_cmd_interpretor* check_cmd_interpretor_switch_or_effective_cmd_interpretor(gchar** readlineResult, stdin_cmd_interpretor** effective_cmd_interpretor_pointer, gboolean* execInNewTerm, gchar** cmd_prompt, gchar** addon_dir){
  int prefix_len,len;
  gboolean cmd_interpretor_activationKey_found_as_prefix_of_readlineResult = FALSE;
  for (guint i = 0; i < G_N_ELEMENTS(addon_stdin_cmd_interpretors); i++) {
                    prefix_len = strlen(addon_stdin_cmd_interpretors[i].activationKey);
		    len = strlen(*readlineResult);
		    if (g_strcmp0(*readlineResult, addon_stdin_cmd_interpretors[i].activationKey)==0){//检测 a> 切换到AI
		       save_current_history();
		       snprintf(rfm_historyFile_ParentDirectory,sizeof(rfm_historyFile_ParentDirectory),addon_resource.addon_dir);
		       load_history_for_prompt();
		       g_free(*cmd_prompt);
		       *cmd_prompt = strdup(AI_models[current_AI_model_index].term_prompt);
		       g_debug("addon_stdin_cmd_interpretor:%s, current_model term_prompt:%s",addon_stdin_cmd_interpretors[i].name, AI_models[current_AI_model_index].term_prompt);
		       return &(addon_stdin_cmd_interpretors[i]);//如果当前输入命令是选择命令解释器,选择完后就直接进入一轮命令读取,无需再对当前输入命令进一步处理
		    
                    // Check if it has the prefix AND is longer than just the prefix itself
                    }else if (g_str_has_prefix(*readlineResult, addon_stdin_cmd_interpretors[i].activationKey)) { //检测命令包含 a> 前缀
                        memmove(*readlineResult, *readlineResult + prefix_len, len - prefix_len + 1); // Shift string left
                        *effective_cmd_interpretor_pointer = &(addon_stdin_cmd_interpretors[i]); // Set effective interpreter for this command
                        cmd_interpretor_activationKey_found_as_prefix_of_readlineResult = TRUE;
                        g_debug("effective addon_stdin_cmd_interpretor %s,current_model term_prompt:%s",addon_stdin_cmd_interpretors[i].name, AI_models[current_AI_model_index].term_prompt);
                        break; // Found the prefix, stop checking
                    }
  }
  
  if (!cmd_interpretor_activationKey_found_as_prefix_of_readlineResult){
        for(guint i=0;i<AI_models_array_size;i++){
		  prefix_len = strlen(AI_models[i].activationKey);
                  if (g_strcmp0(*readlineResult, AI_models[i].activationKey)==0){ //检测命令用类似 c>, d> 等切换到特定AI model
		    save_current_history();
                    current_AI_model_index = i;
		    load_history_for_prompt();
		    g_free(*cmd_prompt);
		    *cmd_prompt = strdup(AI_models[current_AI_model_index].term_prompt);
		    g_debug("current_model term_prompt:%s",AI_models[current_AI_model_index].term_prompt);
                    return &(addon_stdin_cmd_interpretors[0]);
                  }else if (g_str_has_prefix(*readlineResult, AI_models[i].activationKey)){ //检测命令包含类似 c>, d>前缀
                        memmove(*readlineResult, *readlineResult + prefix_len, len - prefix_len + 1); // Shift string left
                        *effective_cmd_interpretor_pointer = &(addon_stdin_cmd_interpretors[0]); // Set effective interpreter for this command
		        current_tmp_prefix_AI_model_index = i;
                        g_debug("prefix_found,current_model term_prompt:%s",AI_models[current_tmp_prefix_AI_model_index].term_prompt);
                        break; // Found the prefix, stop checking
		  }
        }
  }
  if (*effective_cmd_interpretor_pointer == &(addon_stdin_cmd_interpretors[0])){
        *execInNewTerm = current_tmp_prefix_AI_model_index>=0 ? AI_models[current_tmp_prefix_AI_model_index].InNewTerm : AI_models[current_AI_model_index].InNewTerm;
   	setenv("rfm_AI_MODEL_prompt",*readlineResult,1);
        *addon_dir = g_strdup(addon_resource.addon_dir);
	g_debug("addon cmd interpretor set addon_dir to %s", *addon_dir);
  }
  return NULL;
}


static void parse_ai_models_config(const char *json_str) {
    JsonParser *parser = json_parser_new();
    GError *error = NULL;
    
    if (!json_parser_load_from_data(parser, json_str, -1, &error)) {
        g_warning("JSON parse error: %s", error->message);
        g_error_free(error);
        g_object_unref(parser);
        return;
    }

    JsonNode *root = json_parser_get_root(parser);
    if (!JSON_NODE_HOLDS_ARRAY(root)) {
        g_warning("Expected JSON array");
        g_object_unref(parser);
        return;
    }

    JsonArray *array = json_node_get_array(root);
    AI_models_array_size = json_array_get_length(array);
    if (AI_models_array_size == 0) {
        g_debug("no AI_models");
        g_object_unref(parser);
        return;
    }

    AI_models = calloc(AI_models_array_size,sizeof(AI_model_parameter));
    if (!AI_models) {
        g_warning("Memory allocation failed");
        g_object_unref(parser);
        return;
    }

    for (size_t i = 0; i < AI_models_array_size; i++) {
        JsonNode *item_node = json_array_get_element(array, i);
        if (!JSON_NODE_HOLDS_OBJECT(item_node)) continue;
        
        JsonObject *item = json_node_get_object(item_node);

        // 从JSON中提取各个字段
        const gchar *model_name = json_object_get_string_member(item, "model_name");
        const gchar *calling_script = json_object_get_string_member(item, "calling_script");
        const gchar *term_prompt = json_object_get_string_member(item, "term_prompt");
        const gchar *activationKey = json_object_get_string_member(item, "activationKey");
        gboolean InNewTerm = json_object_get_boolean_member(item, "InNewTerm");
        gint Temperature = json_object_get_int_member(item, "Temperature");
        const gchar *host = json_object_get_string_member(item, "host");
        const gchar *base_url = json_object_get_string_member(item, "base_url");
        const gchar *api_key = json_object_get_string_member(item, "api_key");

        // 填充结构体
        AI_models[i].model_name = model_name ? g_strdup(model_name) : g_strdup("");
        AI_models[i].calling_script = calling_script ? g_strdup(calling_script) : g_strdup("");
        AI_models[i].term_prompt = term_prompt ? g_strdup(term_prompt) : g_strdup("");
        AI_models[i].activationKey = activationKey ? g_strdup(activationKey) : g_strdup("");
        AI_models[i].InNewTerm = InNewTerm;
        AI_models[i].model_temporature = g_strdup_printf("%d",Temperature);
        //g_strlcpy(AI_models[i].host, host ? host : "", sizeof(AI_models[i].host));
        AI_models[i].host_url = base_url ? g_strdup(base_url) : g_strdup("");
        AI_models[i].API_key = api_key ? g_strdup(api_key) : g_strdup("");

	g_debug("    %s    %s, Host: %s, Model: %s, Temp: %s, Script: %s",AI_models[i].activationKey,AI_models[i].term_prompt,AI_models[i].host_url, AI_models[i].model_name ? AI_models[i].model_name : "Default", AI_models[i].model_temporature,AI_models[i].calling_script ? AI_models[i].calling_script : "Default");
    }

    g_object_unref(parser);
}

void ai_models_config_callback(RFM_ChildAttribs *data) {
    if (data){
      g_debug("ai_models_config_callback called");
      char *json_str = data->stdOut;
      parse_ai_models_config(json_str);
    }else{
      g_debug("ai_models_config_callback called with null data");
      return;
    }
}

#define AllAiModels "shared_scripts/merge_model_configs.sh"
static const char *AllAiModels_cmd[] = {AllAiModels, NULL};

static gboolean load_ai_models_config() {
    //g_spawn_wrapper(argv, NULL, TRUE, NULL, NULL, FALSE, ai_models_config_callback, NULL, TRUE);
    RFM_ChildAttribs *child_attribs=calloc(1,sizeof(RFM_ChildAttribs));
    child_attribs->customCallBackFunc=ai_models_config_callback;
    child_attribs->RunCmd = AllAiModels_cmd;
    child_attribs->spawn_async = FALSE;
    child_attribs->name=g_strdup(AllAiModels);
    child_attribs->output_read_by_program=TRUE;
    child_attribs->workingDir = g_strdup(addon_resource.addon_dir);
    return g_spawn_wrapper_(NULL,NULL,child_attribs);
}

static const char *findDirectlyRelatedMessages[] = { NULL, "shared_scripts/rfmAiFindRelatedMessages_Direct.sh %s "RFM_SearchResultTypeNamePrefix"multitype", NULL };
static const char *findRelatedMessages[] = { NULL, "shared_scripts/rfmAiFindRelatedMessages.sh %s "RFM_SearchResultTypeNamePrefix"multitype", NULL };
static const char *findRelatedDMessagesAndAttachedFiles[] = { NULL, "shared_scripts/rfmAIAddAttachedFilesToMessageFileList.sh %s shared_scripts/rfmAiFindRelatedMessages_Direct.sh "RFM_SearchResultTypeNamePrefix"multitype", NULL};
static const char *findRelatedMessagesAndAttachedFiles[] = { NULL, "shared_scripts/rfmAIAddAttachedFilesToMessageFileList.sh %s shared_scripts/rfmAiFindRelatedMessages.sh "RFM_SearchResultTypeNamePrefix"multitype", NULL};
static const char *AiDialogAddAttachedFiles[] = { NULL, "shared_scripts/rfmAIAddAttachedFilesToMessageFileList.sh %s "RFM_SearchResultTypeNamePrefix"multitype", NULL};
static const char *AllAiModels_SearchResult[] = {NULL, "find . -name AI_model.json -exec readlink -f {} +" RFM_SearchResultTypeNamePrefix"multitype"};
static const char *AllAiMessages[] = { NULL, "locate --regex '[.|/]rfmAI.md$'|sed -E 's/(.*___)([0-9]{8})(_.*)/\\1\\2\\3:\\2/'|sort -t ':' -k2,2 -r"RFM_SearchResultTypeNamePrefix"multitype", NULL };
//上面原始命令是这样的，\\1\\2\\3是为了转义\。sed部分代码是识别文件名___后的8位日期数字，加冒号后添加到行未： locate '*.rfmAI.md'|sed -E 's/(.*___)([0-9]{8})(_.*)/\1\2\3:\2/'>multitype
static const char *TreeAiMessages[] = { NULL, "find \"$rfm_curPath\" -name 'rfmAI*rfmAI.md'|sed -E 's/(.*___)([0-9]{8})(_.*)/\\1\\2\\3:\\2/'|sort -t ':' -k2,2 -r"RFM_SearchResultTypeNamePrefix"multitype", NULL };


RFM_ToolButton tool_buttons_local[] = {
   /* name           icon                       function    		RunCmd      SearchResultView   DirectoryView   Accel                 tooltip                showCondition*/
   { ALL_AI_DIALOG,  NULL,                      NULL,                   AllAiMessages,     TRUE,           TRUE,      0,                      NULL,                   NULL},
   { TREE_AI_DIALOG, NULL,                      NULL,                   TreeAiMessages,    FALSE,          TRUE,      0,                      NULL,                   NULL},
   { "All Ai Models",NULL,                      NULL,                   AllAiModels_SearchResult,TRUE,     TRUE,      0,                      NULL,                   NULL},
};

RFM_MenuItem file_menus_local[] = {
   /* name           mime root        mime sub type            filenameSuffix           OrSearchResultType	func		runCmd            		showCondition	*/
   { RunActionFindDRelated,"*",        "*",                    ".rfmAI.md",             NULL,                   NULL,           findDirectlyRelatedMessages,    NULL },
   { RunActionFindRelated, "*",        "*",                    ".rfmAI.md",             NULL,                   NULL,           findRelatedMessages,            NULL },
   { RunActionFindDRelatedAndAttachedFiles, "*", "*",          ".rfmAI.md",             NULL,                   NULL,           findRelatedDMessagesAndAttachedFiles, NULL },
   { RunActionFindRelatedAndAttachedFiles, "*", "*",           ".rfmAI.md",             NULL,                   NULL,           findRelatedMessagesAndAttachedFiles, NULL },
   { RunActionAddAttachedFiles, "*",    "*",                   ".rfmAI.md",             NULL,                   NULL,           AiDialogAddAttachedFiles,       NULL },
};

#ifdef CmarkTools
#define getAiPrompt "extractKeyValuePairFromMarkdown 0 %s 用户提示词 | grep '^用户提示词'| sed 's/^[^=]*=//'"
        //返回 用户提示词=blablabal 字符串，匹配以任意字符开头直到第一个等号的部分（包括等号），然后将其替换为空。
#endif
RFM_treeviewColumn treeviewColumns[] = {
#ifdef CmarkTools
  {"AiPrompt",                NUM_COLS,                   FALSE,  NULL, NULL,                 NUM_COLS,                 getAiPrompt,     NULL,     "text",      "markdown",FALSE,FALSE,".rfmAI.md"},
#endif
};


#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wincompatible-pointer-types"
static const char* single_model_config_cmd[]={ NULL,"shared_scripts/get_single_model_config.sh %s",NULL};
RFM_SearchResultType searchresultTypes[] = {
//name	              SearchResultLineProcessingFunc                  showcolumn     SearchResultColumnSeperator  cmdTemplate                                Description
{ "AI_model.json",   ProcessJsonInCmdOutputFromSearchResult,           NULL,          NULL,                       single_model_config_cmd,                    "" },
};
#pragma GCC diagnostic pop


void rfm_local_enter(char * Addon_dir) {
     fprintf(stderr,"rfm_local_enter called! dir:%s\n",Addon_dir);

     addon_resource.addon_dir = strdup(Addon_dir);
     addon_resource.toolbar_button_definitions_local = tool_buttons_local;
     addon_resource.toolbar_button_definitions_local_array_size = G_N_ELEMENTS(tool_buttons_local);
     addon_resource.file_menuitem_definitions_local = file_menus_local;
     addon_resource.file_menuitem_definitions_local_array_size = G_N_ELEMENTS(file_menus_local);
     addon_resource.searchresulttypes = searchresultTypes;
     addon_resource.searchresulttypes_array_size = G_N_ELEMENTS(searchresultTypes);
     addon_resource.check_cmd_interpretor_switch_or_effective_cmd_interpretor=check_cmd_interpretor_switch_or_effective_cmd_interpretor;
     addon_resource.treeviewColumns = treeviewColumns;
     addon_resource.treeviewColumns_array_size = G_N_ELEMENTS(treeviewColumns);
     AllAiMessages[0] = stdin_cmd_interpretors[0].name;
     AllAiModels_SearchResult[0] = stdin_cmd_interpretors[0].name;
     TreeAiMessages[0] = stdin_cmd_interpretors[0].name;
     findDirectlyRelatedMessages[0] = stdin_cmd_interpretors[0].name;
     findRelatedMessages[0] = stdin_cmd_interpretors[0].name;
     findRelatedDMessagesAndAttachedFiles[0] = stdin_cmd_interpretors[0].name;
     findRelatedMessagesAndAttachedFiles[0] = stdin_cmd_interpretors[0].name;
     AiDialogAddAttachedFiles[0] = stdin_cmd_interpretors[0].name;
     single_model_config_cmd[0] = stdin_cmd_interpretors[0].name;
     add_toolbar_buttons(&(addon_resource.tool_bar_buttons_local),tool_buttons_local, addon_resource.toolbar_button_definitions_local_array_size);
     add_file_menuitems(&(addon_resource.menuItems_local), file_menus_local, addon_resource.file_menuitem_definitions_local_array_size);
     load_ai_models_config();
}

void rfm_local_leave() {
     fprintf(stderr,"rfm_local_leave called for %s!\n",addon_resource.addon_dir);
     remove_toolbar_buttons(&(addon_resource.tool_bar_buttons_local),addon_resource.toolbar_button_definitions_local_array_size);
     remove_file_menuitems(&(addon_resource.menuItems_local), addon_resource.file_menuitem_definitions_local_array_size);
}
